{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# First attempt at learning"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "import plotly\n",
    "import plotly.graph_objs as go\n",
    "plotly.offline.init_notebook_mode()\n",
    "import os\n",
    "import random\n",
    "import math\n",
    "from tqdm import tqdm\n",
    "from sklearn.metrics import mean_squared_error\n",
    "from scipy.stats import spearmanr\n",
    "import pymc3 as pm\n",
    "import theano\n",
    "theano.config.compute_test_value = 'raise'\n",
    "%matplotlib inline\n",
    "\n",
    "SELECTED_DATA_DIR = \"../selected-data/\"\n",
    "MOVIES_FILE = \"best_movie_ratings_features_engineered.csv\"\n",
    "USERS_FILE = \"users_ratings.csv\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Read data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "movies = pd.read_csv(SELECTED_DATA_DIR + MOVIES_FILE, index_col=0)\n",
    "movies.rating = movies.rating/10\n",
    "movies.sample()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "users = pd.read_csv(SELECTED_DATA_DIR + USERS_FILE, index_col=0)\n",
    "users.rating = users.rating/10\n",
    "users.sample()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Learning"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def utility(user_features, movie_features, epoch, s=1):\n",
    "    \"\"\" Compute utility based on user preferences and movie preferences \"\"\"\n",
    "    return user_features.dot(movie_features) * (1 - math.exp(-epoch/s))\n",
    "\n",
    "def get_movie_features(movie):\n",
    "    \"\"\" selected features from dataframe \"\"\"\n",
    "    if isinstance(movie, pd.Series):\n",
    "        return movie[-50:]\n",
    "    elif isinstance(movie, pd.DataFrame):\n",
    "        return get_movie_features(movie.loc[movie.index[0]])\n",
    "    else:\n",
    "        raise TypeError(\"{} should be a Series or DataFrame\".format(movie))\n",
    "    \n",
    "def best_recommandation(user_features, movies, epoch):\n",
    "    \"\"\" Return the movie with the highest utility \"\"\"\n",
    "    utilities = np.zeros(movies.shape[0])\n",
    "    for i, (title, movie) in enumerate(movies.iterrows()):\n",
    "        movie_features = get_movie_features(movie)\n",
    "        utilities[i] = utility(user_features, movie_features, epoch - movie.last_t)\n",
    "    return movies[movies.index == movies.index[utilities.argmax()]]\n",
    "\n",
    "def all_recommandation(user_features, movies):\n",
    "    \"\"\" Return all movies sorted by utility \"\"\"\n",
    "    movies = movies.copy()\n",
    "    movies['utilities'] = movies.apply(lambda mov: utility(user_features, get_movie_features(mov), 1000), axis=1)\n",
    "    return movies.sort_values(by=\"utilities\")\n",
    "\n",
    "\n",
    "def greedy_choice(user_features, movies, epoch):\n",
    "    \"\"\" greedy approach to the problem \"\"\"\n",
    "    epsilon = 1 / math.sqrt(epoch+1)\n",
    "    if random.random() > epsilon: # choose the best\n",
    "        return best_recommandation(user_features, movies, epoch)\n",
    "    else:\n",
    "        return movies.sample()\n",
    "\n",
    "def greedy_choice_no_t(user_features, movies, epsilon=0.5):\n",
    "    \"\"\" greedy approach to the problem \"\"\"\n",
    "    if random.random() > epsilon: # choose the best\n",
    "        return best_recommandation(user_features, movies)\n",
    "    else:\n",
    "        return movies.sample()\n",
    "        \n",
    "def iterative_mean(old, new, t):\n",
    "    \"\"\" Compute the new mean \"\"\"\n",
    "    return ((t-1) / t) * old + (1/t) * new\n",
    "    \n",
    "def update_features(user_features, movie_features, rating, t):\n",
    "    \"\"\" update the user preferen \"\"\"\n",
    "    return iterative_mean(user_features, movie_features * rating, t+1)\n",
    "\n",
    "def train_user(user, movies):\n",
    "    user_features = np.zeros(movies.shape[1] - 2)\n",
    "    movies = movies.copy()\n",
    "    movies.insert(0, 'last_t', np.ones(movies.shape[0]).astype(np.int64))\n",
    "    for t in tqdm(range(1000)):\n",
    "        recommandation = greedy_choice(user_features, movies, t)\n",
    "        recommandation_features = get_movie_features(recommandation)\n",
    "        user_rating = user.get_value(recommandation.index[0], \"rating\")\n",
    "        user_features = update_features(user_features, recommandation_features, user_rating, t)\n",
    "        movies.loc[movies.index.isin(recommandation.index),'last_t'] = t\n",
    "    return user_features\n",
    "\n",
    "def test_user(user, user_features, movies):\n",
    "    allrec = all_recommandation(user_features, movies).utilities.sort_index()\n",
    "    user_ratings = user[user.index.isin(movies.index)].sort_values(by=\"rating\").rating.sort_index()\n",
    "    print(spearmanr(allrec.rank(), user_ratings.rank()))\n",
    "    return math.sqrt(mean_squared_error(allrec.as_matrix(), user_ratings.as_matrix()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## One user"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Selection"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "user = users[users.user.isin(users.user.sample())]\n",
    "user.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# get only movies that this user rated\n",
    "movies_user = movies[movies.index.isin(user.index)]\n",
    "movies_user.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Split Train/Test"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "msk = np.random.rand(movies_user.shape[0]) < 0.8\n",
    "train = movies_user.loc[msk]\n",
    "test = movies_user.loc[~msk]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "user_features = train_user(user, train)\n",
    "user_features"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Score"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "error = test_user(user, user_features, test)\n",
    "error"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Multiple users"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "N_USER=10\n",
    "res_score = []\n",
    "for i in tqdm(range(N_USER)):\n",
    "    user = users[users.user.isin(users.user.sample())]\n",
    "    movies_user = movies[movies.index.isin(user.index)]\n",
    "    msk = np.random.rand(movies_user.shape[0]) < 0.8\n",
    "    train = movies_user.loc[msk]\n",
    "    test = movies_user.loc[~msk]\n",
    "    user_features = train_user(user, train)\n",
    "    error = test_user(user, user_features, test)\n",
    "    res_score.append(error)\n",
    "\n",
    "sum(res_score)/len(res_score)    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "## Try Bayesian inference"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "size = 10\n",
    "theta_prime = np.random.rand(size)\n",
    "x = np.random.rand(size)\n",
    "\n",
    "c0 = 10\n",
    "d0 = 3\n",
    "e0 = 0.01\n",
    "f0 = 0.001\n",
    "g0 = 0.001\n",
    "\n",
    "t = 1\n",
    "\n",
    "I = np.eye(size)\n",
    "print(theta_prime)\n",
    "print(x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "with pm.Model():\n",
    "    s = pm.Gamma('s', d0, e0)\n",
    "    sigma = pm.InverseGamma('sigma', f0, g0)\n",
    "    theta = pm.MvNormal('theta', mu=0.5, cov=c0 * sigma * I)\n",
    "\n",
    "    # Expected value of outcome\n",
    "    mu = theta_prime.dot(x) * (1 - np.exp(-t/s))\n",
    "\n",
    "    # Likelihood (sampling distribution) of observations\n",
    "    rating = pm.Normal('rating', mu=mu, sd=sigma, observed=theta_prime)\n",
    "    \n",
    "    step = pm.Metropolis()\n",
    "    trace = pm.sample(1000, step=step)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "pm.summary(trace)\n",
    "pm.traceplot(trace)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "rating.distribution.random()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "theta.distribution.random()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
